/*
 * Albareth - an action roleplaying game.
 * Copyright (C) 2008 Jens Anuth
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 *   02110-1301, USA.
 * 
 * $Id: BasicMenuFrame.java 47 2008-05-16 21:01:33Z jens464 $
*/
package albareth.frontend.slick.ui;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;

import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;

import frosch.util.Coordinates;

public class BasicMenuFrame extends UIElement
{
    //the keyboard commands that menus understand:
    private static final int CMD_UP    = Input.KEY_UP;
    private static final int CMD_UP2   = Input.KEY_W;
    private static final int CMD_DN    = Input.KEY_DOWN;
    private static final int CMD_DN2   = Input.KEY_S;
    private static final int CMD_LEFT    = Input.KEY_LEFT;
    private static final int CMD_LEFT2   = Input.KEY_A;
    private static final int CMD_RIGHT    = Input.KEY_RIGHT;
    private static final int CMD_RIGHT2   = Input.KEY_D;

    private static final int CMD_USE   = Input.KEY_RETURN;
    private static final int CMD_USE2  = Input.KEY_SPACE;
    private static final int CMD_CLOSE = Input.KEY_ESCAPE;
    private static final int CMD_PAGE_UP = 201;
    private static final int CMD_PAGE_DN = 209;
    
    private MenuView _view;
    
    private ArrayList _items = new ArrayList();
    private int _currentSelectedIdx = 0;

    //components listening to events from this menu
    private LinkedList _listeners = new LinkedList();

    //a page counter
    private Coordinates _pageCounterLocation = new Coordinates(0, 0);
    private boolean _pageCounterVisible = false;
    private int _currentPage = 0;
    private boolean _selectionUpdatedOnMouseOver = false;
    private boolean _selectOnMousePress = false;
    
    public BasicMenuFrame(MenuView view)
    {
        _view = view;
    }

    public MenuView getView()
    {
        return _view;
    }

    public void setView(MenuView view)
    {
        _view = view;
    }

    public int getCurrentSelectedIndex()
    {
        return _currentSelectedIdx;
    }
    
    public void setCurrentSelectedIndex(int index)
    {
        _currentSelectedIdx = index;
    }
    
    public int getNumItems()
    {
        return _items.size();
    }

    /**
     * Positions the page counter at bottom right.
     */
    public void doLayout()
    {
        String counterString = getPageCounterString();
        int counterWidth = getFont().getWidth(counterString);
        int counterHeight = getFont().getLineHeight();
        _pageCounterLocation = new Coordinates(
                _componentWidth - counterWidth,
                _componentHeight - counterHeight
        );
    }
    
    private String getPageCounterString()
    {
        return (_currentPage + 1) + "/" + getNumPages();
    }
    
    public int getNumPages()
    {
        int perPage = _view.getItemsPerPage(this);//getEntriesPerPage();
        int pages = _items.size() / perPage;
        int overflow = _items.size() % perPage;
        if (overflow > 0) {
            pages++;
        }
        return pages;
    }

    /*
    public int getEntriesPerPage()
    {
        return _view.getRows(this) * _view.getColumns(this);
    }*/

    public Coordinates getPageCounterLocation()
    {
        return _pageCounterLocation;
    }

    public void setPageCounterLocation(Coordinates location)
    {
        _pageCounterLocation = location;
    }

    public boolean isPageCounterVisible()
    {
        return _pageCounterVisible;
    }

    public void setPageCounterVisible(boolean visible)
    {
        _pageCounterVisible = visible;
    }
    
    public void addMenuListener(MenuListener listener)
    {
        _listeners.add(listener);
    }
    
    private void notifyListenersCloseRequested()
    {

        for (Iterator it = _listeners.iterator(); it.hasNext(); ) {
            MenuListener listener = (MenuListener)it.next();
            listener.menuCloseRequested(this);
        }
    }
    
    private void notifyListenersItemSelected()
    {
        if (_currentSelectedIdx == -1) {
            return;
        }
        MenuItem selectedItem = (MenuItem)_items.get(_currentSelectedIdx);
        for (Iterator it = _listeners.iterator(); it.hasNext(); ) {
            MenuListener listener = (MenuListener)it.next();
            listener.menuItemSelected(this, selectedItem);
        }

    }
    
    public final void addItem(String text, int id)
    {
        MenuItem item = new MenuItem(text);
        item.setId(id);
        addItem(item);
    }
    
    public void addItem(MenuItem item)
    {
        item.setOwnerComponent(this);
        _items.add(item);
    }
    
    public void replaceItem(int index, MenuItem item)
    {
        item.setOwnerComponent(this);
        _items.set(index, item);
    }
    
    public MenuItem getItem(int index)
    {
        return (MenuItem)_items.get(index);
    }
    
    public final boolean removeItemById(int id)
    {
        for (Iterator it = _items.iterator(); it.hasNext(); )
        {
            MenuItem item = (MenuItem)it.next();
            if (item.getId() == id) {
                it.remove();
                return true;
            }
        }
        return false;
    }
    
    public boolean removeItemByObj(Object representedObj)
    {
        for (Iterator it = _items.iterator(); it.hasNext(); )
        {
            MenuItem item = (MenuItem)it.next();
            if (item.getReferencedObject() == representedObj)
            {
                it.remove();
                return true;
            }
        }
        return false;
    }
    
    public final boolean removeItem(MenuItem item)
    {
        return _items.remove(item);
    }
    
    public final void removeAllItems()
    {
        _items.clear();
    }

    
    public final int getSelectedId()
    {
        if (_currentSelectedIdx == -1) {
            return -1;
        }
        MenuItem item = (MenuItem)_items.get(_currentSelectedIdx);
        if (item == null) {
            return -1;
        }
        return item.getId();
    }
    
    public final Object getSelectedObject()
    {
        if (_currentSelectedIdx == -1) {
            return null;
        }
        MenuItem item = (MenuItem)_items.get(_currentSelectedIdx);
        if (item == null) {
            return null;
        }
        return item.getReferencedObject();
    }
    
    
    public int getCurrentPageStartIndex()
    {
        return _currentPage * _view.getItemsPerPage(this);
    }
    
    public int getCurrentPageEndIndex()
    {
        int endIdx = (_currentPage + 1) * _view.getItemsPerPage(this);
        if (endIdx > _items.size()) {
            endIdx = _items.size();
        }
        return endIdx;
    }
    
    protected boolean keyPressed(int key, char c)
    {
        int pageOffset;        
        switch (key)
        {
            case CMD_UP:
            case CMD_UP2:
                _currentSelectedIdx--;
                if (_currentSelectedIdx < 0) {
                    _currentSelectedIdx = _items.size() - 1;
                }
                updatePage();
                break;
            case CMD_DN:
            case CMD_DN2:
                _currentSelectedIdx = 
                    (_currentSelectedIdx + 1) % _items.size();
                updatePage();
                break;
            case CMD_LEFT:
            case CMD_LEFT2:
                pageOffset = _currentSelectedIdx - getCurrentPageStartIndex();
                pageOffset -= _view.getRowsPerPage(this);
                if (pageOffset < 0) {
                    pageOffset += _view.getItemsPerPage(this);
                }
                _currentSelectedIdx = getCurrentPageStartIndex() + pageOffset;
                break;
            case CMD_RIGHT:
            case CMD_RIGHT2:
                pageOffset = _currentSelectedIdx - getCurrentPageStartIndex();
                pageOffset = (pageOffset + _view.getRowsPerPage(this)) % _view.getItemsPerPage(this);
                _currentSelectedIdx = getCurrentPageStartIndex() + pageOffset;
                break;
            case CMD_PAGE_UP:
                if (getNumPages() > 1)
                {
                    int nextPage = _currentPage - 1;
                    if (nextPage < 0) {
                        nextPage = getNumPages() - 1;
                    }
                    _currentSelectedIdx = nextPage * _view.getItemsPerPage(this);
                    updatePage();
                }
                break;
            case CMD_PAGE_DN:
                if (getNumPages() > 1)
                {
                    _currentSelectedIdx = (_currentPage + 1) * _view.getItemsPerPage(this);
                    if (_currentSelectedIdx > _items.size()) {
                        _currentSelectedIdx = 0;
                    }
                    updatePage();
                }
                break;
            case CMD_USE:
            case CMD_USE2:
                notifyListenersItemSelected();
                break;
            case CMD_CLOSE:
                notifyListenersCloseRequested();
                break;
            default:
        }
        return true;
    }
    
    private void updatePage()
    {
        _currentPage = _currentSelectedIdx / _view.getItemsPerPage(this);
    }

    protected boolean mousePressed(int button, int x, int y)
    {
        if (_selectOnMousePress) {
            selectItemAt(x, y);
            return true;
        }
        return false;

    }

    protected boolean mouseClicked(int button, int x, int y)
    {
        if (!_selectOnMousePress) {
            selectItemAt(x, y);
            return true;
        }
        return false;
    }
    
    private void selectItemAt(int x, int y)
    {
        int index = getItemIndexAt(x, y);
        if (index < 0 ||
            index >= getNumItems() ||
            getItem(index).isDisabled())
        {
            return;
        }
        _currentSelectedIdx = index; 
        notifyListenersItemSelected();
    }


    /**
     * 
     * @return the valid index or -1 if it is out of range.
     */
    public int getItemIndexAt(int x, int y)
    {
        int itemPageOffset = getItemPageOffsetAt(x, y);
        int index = getCurrentPageStartIndex() + itemPageOffset;
        return index >= _items.size() ? - 1 : index;
    }
    
    public int getItemPageOffsetAt(int x, int y)
    {
        return _view.getItemPageOffsetAt(this, x - getPaddingX(), y - getPaddingY());
    }
    
    /**
     * 
     * @return The item or null if the location contains no items.
     */
    public MenuItem getItemAt(int x, int y)
    {
        int index = getItemIndexAt(x, y);
        return index < 0 ? null : getItem(index);
    }
    
    protected boolean mouseMoved(int newx, int newy)
    {
        if (_selectionUpdatedOnMouseOver ) {
            //-1 is allowed as current index.
            _currentSelectedIdx = getItemIndexAt(newx, newy); 
        }

        return true;
    }

    protected void mouseLeft(int newx, int newy)
    {
        if (_selectionUpdatedOnMouseOver) {
            _currentSelectedIdx = -1;
        }
    }



    public void render(Graphics g, GameContainer container, int screenX, int screenY)
            throws SlickException
    {
        //int rowsPerPage = _view.getRows(this);
        //int itemsPerPage = getEntriesPerPage();
        int pageStartIdx = getCurrentPageStartIndex();
        int pageEndIdx = getCurrentPageEndIndex();
        for (int i = pageStartIdx; i < pageEndIdx; i++)
        {
            MenuItem item = (MenuItem)_items.get(i);

            int itemPageOffset = i - pageStartIdx;
            int offsetX = _view.getItemXPosition(this, itemPageOffset); 
            int offsetY = _view.getItemYPosition(this, itemPageOffset);
            _view.renderMenuItem(
                    this, item, g,
                    screenX + offsetX, screenY + offsetY,
                    i == _currentSelectedIdx
            );
        }

        
        if (_pageCounterVisible) {
            g.setFont(getFont());
            g.setColor(getForegroundColor());
            g.drawString(
                    getPageCounterString(),
                    screenX + _pageCounterLocation.x,
                    screenY + _pageCounterLocation.y
            );
        }

    }

    public String getTooltipText(int x, int y)
    {
        int offset = getItemPageOffsetAt(x, y);
        int idx = getCurrentPageStartIndex() + offset;
        if (idx < 0 || idx >= getNumItems()) {
            return null;
        }
        return getItem(idx).getTooltipText();
    }
    
    public DraggeableItem getDraggeableItem(int x, int y)
    {
        if (isDraggingAllowed() == false) {
            return null;
        }
        int offset = getItemPageOffsetAt(x, y);
        int idx = getCurrentPageStartIndex() + offset;
        if (idx < 0 || idx >= getNumItems()) {
            return null;
        }
        return getItem(idx);
    }

    public boolean isSelectionUpdatedOnMouseOver()
    {
        return _selectionUpdatedOnMouseOver;
    }

    public void setSelectionUpdatedOnMouseOver(boolean selectionUpdatedOnMouseOver)
    {
        _selectionUpdatedOnMouseOver = selectionUpdatedOnMouseOver;
    }

    /**
     * If true, menu items are selected on mouse down, else they are
     * selected on mouse click, that means on mouse up after mouse down.
     */
    public void selectOnMousePress(boolean b)
    {
        _selectOnMousePress  = b;
    }
    

    
}
